/*
 * wifi_svc.c
 *
 *  Created on: Nov 1, 2020
 *      Author: zchaojian
 */

#include "wifi_svc.h"
#include "config_app.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_smartconfig.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <stdio.h>
#include <string.h>
// #include "dhcpserver.h"

// ap
#define EXAMPLE_MAX_STA_CONN 4
#define EXAMPLE_ESP_WIFI_CHANNEL 1

// sta
#define EXAMPLE_ESP_MAXIMUM_RETRY 5
/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define ESPTOUCH_DONE_BIT BIT2
static unsigned int iServerRetryNum = 0;

static uint8_t mIpGot = 0;

static const char* TAG = "wifi service";
static const char* AP_TAG = "wifi ap";
static const char* STA_TAG = "wifi sta";
static const char* APSTA_TAG = "wifi apsta";
static const char* SC_TAG = "wifi smart config";

/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;

typedef struct
{
    WIFI_vEvent_CallBack tCallback;
    WIFIEventTPDF tEvent;
} WifiProcessTPDF;
WifiProcessTPDF mWifiProcess = { 0 };

static uint32_t Wifi_dConvert_To_IP(uint16_t usIpH, uint16_t usIpL)
{
    uint8_t i[4];
    i[0] = (uint8_t)(usIpH >> 8) & 0x00ff;
    i[1] = (uint8_t)usIpH & 0x00ff;
    i[2] = (uint8_t)(usIpL >> 8) & 0x00ff;
    i[3] = (uint8_t)usIpL & 0x00ff;
    return (i[0] + (i[1] << 8) + (i[2] << 16) + (i[3] << 24));
}

/* convert modbus data  */
static void vConvert_MB_HiLo_Byte(char* cDataIn, uint8_t uiLen)
{
    char temp;
    uint8_t len = uiLen;
    if (uiLen % 2 == 1) {
        len = uiLen + 1;
    }
    for (int i = 0; i < len; i += 2) {
        temp = cDataIn[i];
        cDataIn[i] = cDataIn[i + 1];
        cDataIn[i + 1] = temp;
    }
}

/* convert modbus data  */
static void vConvert_MB_HiLo_Byte_Uin8t(uint8_t* cDataIn, uint8_t uiLen)
{
    char temp;
    uint8_t len = uiLen;
    if (uiLen % 2 == 1) {
        len = uiLen + 1;
    }
    for (int i = 0; i < len; i += 2) {
        temp = cDataIn[i];
        cDataIn[i] = cDataIn[i + 1];
        cDataIn[i + 1] = temp;
    }
}

static void vWifi_AP_Event_Handler(void* arg, esp_event_base_t event_base,
    int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*)event_data;
        ESP_LOGI(AP_TAG, "station " MACSTR " join, AID=%d", MAC2STR(event->mac), event->aid);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*)event_data;
        ESP_LOGE(AP_TAG, "station " MACSTR " leave, AID=%d", MAC2STR(event->mac), event->aid);
    }
}

static void vWifi_Init_softap(WifiConfigTPDF* tCfg, wifi_init_config_t* pApConfig)
{
    esp_netif_t* ap_netif = esp_netif_create_default_wifi_ap();
    assert(ap_netif);

    *pApConfig = (wifi_init_config_t)WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(pApConfig));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
        &vWifi_AP_Event_Handler, NULL, NULL));
    wifi_config_t wifiApConfig = {
        .ap = {
            /*.ssid = {*tCfg->cApSSID},
            .ssid_len = strlen(tCfg->cApSSID),*/
            .channel = EXAMPLE_ESP_WIFI_CHANNEL,
            //.password = DEFINE_PWD,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK },
    };

    memcpy(wifiApConfig.sta.ssid, tCfg->cApSSID, sizeof(tCfg->cApSSID));
    memcpy(wifiApConfig.sta.password, tCfg->cApPWD, sizeof(tCfg->cApPWD));

    vConvert_MB_HiLo_Byte_Uin8t(wifiApConfig.sta.ssid, sizeof(tCfg->cApSSID));
    vConvert_MB_HiLo_Byte_Uin8t(wifiApConfig.sta.password, sizeof(tCfg->cApPWD));

    if (strlen(tCfg->cApPWD) == 0) {
        wifiApConfig.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifiApConfig));

    tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP);
    tcpip_adapter_ip_info_t tCfgAPPar;
    tCfgAPPar.gw.addr = Wifi_dConvert_To_IP(tCfg->usApIPH, tCfg->usApIPL);
    tCfgAPPar.ip.addr = Wifi_dConvert_To_IP(tCfg->usApIPH, tCfg->usApIPL);
    tCfgAPPar.netmask.addr = Wifi_dConvert_To_IP(tCfg->usApSubMaskH, tCfg->usApSubMaskL);
    tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &tCfgAPPar);
    tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP);

    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(AP_TAG, "wifi_init_softap finished. \n\tSSID:%s password:%s channel:%d",
        tCfg->cApSSID, tCfg->cApPWD, EXAMPLE_ESP_WIFI_CHANNEL);
}

static void vWifi_Callback(void* tTag, WIFIEventTypeTPDF tType, char* cData, uint16_t usLen)
{
    if (mWifiProcess.tCallback) {
        mWifiProcess.tEvent.tType = tType;
        mWifiProcess.tEvent.tTag = tTag;
        mWifiProcess.tEvent.cData = cData;
        mWifiProcess.tEvent.usLen = usLen;
        mWifiProcess.tCallback(&mWifiProcess.tEvent);
        // ESP_LOGE(TAG, "In  vWifi callback");
    }
    // ESP_LOGE(TAG, "Out  vWifi callback");
}

static void vWifi_STA_Event_Handler(void* arg, esp_event_base_t event_base,
    int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        if (mPartitionTable.tWifiConfig.usSmartConfigEnable == WIFI_SC_ENABLE) {
            // stat smart config function
            ESP_LOGI(SC_TAG, "smart configure task start");
            ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH));
            smartconfig_start_config_t sc_cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
            ESP_ERROR_CHECK(esp_smartconfig_start(&sc_cfg));
        } else {
            // connect wifi if own SSID and password
            esp_wifi_connect();
            char* d = NULL;
            vWifi_Callback(event_data, WIFI_STA_START, d, 0);
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (mPartitionTable.tWifiConfig.usSmartConfigEnable == WIFI_SC_DISENABLE) {
            if (iServerRetryNum < EXAMPLE_ESP_MAXIMUM_RETRY) {
                esp_wifi_connect();
                iServerRetryNum++;
                ESP_LOGE(STA_TAG, "retry to connect to the AP");
                vTaskDelay(5000 / portTICK_PERIOD_MS);
            } else {
                xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
                if (mPartitionTable.tWifiConfig.usSsidPwdCorrect != WIFI_SSID_PWD_WRONG) {
                    /* write password correct value to nvs flash */
                    uint32_t uiIndex = 0x004F - 1; // SSID password if correct  register address
                    uint32_t uiSize = sizeof(mPartitionTable.tWifiConfig.usSsidPwdCorrect) >> 1;
                    while (uiSize) {
                        CFG_vSaveConfig((uint16_t)uiIndex++);
                        uiSize--;
                    }
                }
                esp_wifi_connect();
                vTaskDelay(5 * 60 * 1000 / portTICK_PERIOD_MS);
            }
            ESP_LOGE(STA_TAG, "connect to the AP fail");
            mIpGot = 0;
            char* d2 = NULL;
            vWifi_Callback(event_data, WIFI_STA_DISCONNECTED, d2, 0);
        } else {
            ESP_LOGI(TAG, "wi-fi failed connect------try it again..");
            esp_wifi_connect();
            xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_FAILED) {
        ESP_LOGE(TAG, "wi-fi wps fails!!!");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
        ESP_LOGI(STA_TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        iServerRetryNum = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        mIpGot = 1;
        char* d3 = NULL;
        vWifi_Callback(event_data, WIFI_IP_GOT, d3, 0);
        ESP_LOGI(SC_TAG, "WiFi Connected to ap");

        /* save password  right and connect ap */
        if (mPartitionTable.tWifiConfig.usSsidPwdCorrect != WIFI_SSID_PWD_RIGHT) {
            /* write password correct value to nvs flash */
            uint32_t uiIndex = 0x004F - 1; // SSID password if correct  register address
            uint32_t uiSize = sizeof(mPartitionTable.tWifiConfig.usSsidPwdCorrect) >> 1;
            while (uiSize) {
                CFG_vSaveConfig((uint16_t)uiIndex++);
                uiSize--;
            }
        }
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
        ESP_LOGI(SC_TAG, "Scan done");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
        ESP_LOGI(SC_TAG, "Found channel");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
        ESP_LOGI(SC_TAG, "Got SSID and password");

        smartconfig_event_got_ssid_pswd_t* evt = (smartconfig_event_got_ssid_pswd_t*)event_data;
        wifi_config_t wifi_config;
        char cSSID[16];
        char cPWD[16];
        int iSSIDLen;
        int iPWDLen;

        memset(cSSID, 0, sizeof(cSSID));
        memset(cPWD, 0, sizeof(cPWD));

        bzero(&wifi_config, sizeof(wifi_config_t));
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
        wifi_config.sta.bssid_set = evt->bssid_set;
        // copy bssid in wifi config
        if (wifi_config.sta.bssid_set == true) {
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }

        iSSIDLen = strlen((char*)evt->ssid);
        iPWDLen = strlen((char*)evt->password);
        memcpy(cSSID, evt->ssid, sizeof(cSSID));
        memcpy(cPWD, evt->password, sizeof(cPWD));
        // esp_log_buffer_hex(TAG,cSSID, sizeof(cSSID));
        // esp_log_buffer_hex(TAG,cPWD, sizeof(cPWD));

        vConvert_MB_HiLo_Byte(cSSID, (uint8_t)iSSIDLen);
        vConvert_MB_HiLo_Byte(cPWD, (uint8_t)iPWDLen);

        memcpy(mPartitionTable.tWifiConfig.cStaSSID, cSSID, sizeof(mPartitionTable.tWifiConfig.cStaSSID));
        memcpy(mPartitionTable.tWifiConfig.cStaPWD, cPWD, sizeof(mPartitionTable.tWifiConfig.cStaPWD));
        // memcpy(mPartitionTable.tWifiConfig.cStaSSID, cSSID, sizeof(mPartitionTable.tWifiConfig.cStaSSID));
        // memcpy(mPartitionTable.tWifiConfig.cStaPWD, cPWD, sizeof(mPartitionTable.tWifiConfig.cStaPWD));

        /* write will linked sta ssid to nvs flash */
        uint32_t uiIndex = PARTITION_TABLE_ADDR_WIFI_STASSID1_ADDR - 1; // STA SSID start address: 0x003B - 1
        uint32_t uiSize = sizeof(mPartitionTable.tWifiConfig.cStaSSID) >> 1; // 16 chars write to 8 registers.
        while (uiSize) {
            CFG_vSaveConfig((uint16_t)uiIndex++);
            uiSize--;
        }

        /* write will linked sta pwd to nvs flash */
        uiIndex = PARTITION_TABLE_ADDR_WIFI_STAPASSWD1_ADDR - 1; // STA PWD start address:0x0043 - 1
        uiSize = sizeof(mPartitionTable.tWifiConfig.cStaPWD) >> 1; // 16 chars write to 8 registers.
        while (uiSize) {
            CFG_vSaveConfig((uint16_t)uiIndex++);
            uiSize--;
        }

        ESP_LOGI(SC_TAG, "SSID:%s", mPartitionTable.tWifiConfig.cStaSSID);
        ESP_LOGI(SC_TAG, "PASSWORD:%s", mPartitionTable.tWifiConfig.cStaPWD);

        ESP_ERROR_CHECK(esp_wifi_disconnect());
        ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
        ESP_ERROR_CHECK(esp_wifi_connect());
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
    }
}

static void vWifi_Init_sta(WifiConfigTPDF* tCfg, wifi_init_config_t* pStaConfig)
{
    esp_netif_t* sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    *pStaConfig = (wifi_init_config_t)WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(pStaConfig));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    esp_event_handler_instance_t instance_sc_id;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
        &vWifi_STA_Event_Handler,
        NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
        &vWifi_STA_Event_Handler,
        NULL, &instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID,
        &vWifi_STA_Event_Handler,
        NULL, &instance_sc_id));
    wifi_config_t wifiStaConfig = {
        .sta = {
            /*.ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,*/
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,

            .pmf_cfg = {
                .capable = true,
                .required = false },
        },
    };

    /* set ssid and pwd from partition table */
    memcpy(wifiStaConfig.sta.ssid, tCfg->cStaSSID, sizeof(tCfg->cStaSSID));
    memcpy(wifiStaConfig.sta.password, tCfg->cStaPWD, sizeof(tCfg->cStaPWD));

    vConvert_MB_HiLo_Byte_Uin8t(wifiStaConfig.sta.ssid, sizeof(tCfg->cStaSSID));
    vConvert_MB_HiLo_Byte_Uin8t(wifiStaConfig.sta.password, sizeof(tCfg->cStaPWD));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    if (mPartitionTable.tWifiConfig.usSmartConfigEnable == WIFI_SC_DISENABLE) {
        ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifiStaConfig));
    }
    if (tCfg->usStaDHCPMode == WIFI_DHCP_MODE_STATIC) {
        tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
        tcpip_adapter_ip_info_t tCfgStatic;
        tCfgStatic.gw.addr = Wifi_dConvert_To_IP(tCfg->usStaGateWayH, tCfg->usStaGateWayL);
        tCfgStatic.ip.addr = Wifi_dConvert_To_IP(tCfg->usStaIPH, tCfg->usStaIPL);
        tCfgStatic.netmask.addr = Wifi_dConvert_To_IP(tCfg->usStaSubMaskH, tCfg->usStaSubMaskL);
        tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &tCfgStatic);
    }
    ESP_ERROR_CHECK(esp_wifi_start());

    // ESP_LOGI(STA_TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
        WIFI_CONNECTED_BIT | WIFI_FAIL_BIT | ESPTOUCH_DONE_BIT,
        pdFALSE,
        pdFALSE,
        portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & (WIFI_CONNECTED_BIT)) {
        ESP_LOGI(TAG, "wi-fi connected to ap SSID:%s password:%s",
            wifiStaConfig.sta.ssid, wifiStaConfig.sta.password);

        if (mPartitionTable.tWifiConfig.usSmartConfigEnable == WIFI_SC_ENABLE) {
            ESP_LOGI(SC_TAG, "smartconfig over");
            esp_smartconfig_stop();

            /* set smart configure enable status to disenable */
            if (mPartitionTable.tWifiConfig.usSmartConfigEnable != WIFI_SC_DISENABLE) {
                mPartitionTable.tWifiConfig.usSmartConfigEnable = WIFI_SC_DISENABLE;
                CFG_vSaveConfig(0x0050 - 1); // 0x0200 before
            }
            // vEsp32Restart();
        }
    }
    if (bits & WIFI_FAIL_BIT) {
        ESP_LOGE(TAG, "wi-fi Failed to connect to SSID:%s, password:%s",
            wifiStaConfig.sta.ssid, wifiStaConfig.sta.password);
    }
    if (bits & ESPTOUCH_DONE_BIT) {
        ESP_LOGI(SC_TAG, "smartconfig over");
        esp_smartconfig_stop();

        /* set smart configure enable status to disenable */
        mPartitionTable.tWifiConfig.usSmartConfigEnable = WIFI_SC_DISENABLE;
        CFG_vSaveConfig(0x0050 - 1); // 0x0200 before
        vEsp32Restart();
    }
    // else
    // {
    //     ESP_LOGE(STA_TAG, "UNEXPECTED EVENT");
    // }

    /* The event will not be processed after unregister */
    // ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    // ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    // vEventGroupDelete(s_wifi_event_group);
}

/*a callback function to pass wifi connect status*/
void WIFI_vRegister_Callback(WIFI_vEvent_CallBack callback)
{
    mWifiProcess.tCallback = callback;
}

/*Use a event group show wi-fi event, return the handle pointer*/
EventGroupHandle_t* eReturnWifiEventGroup(void)
{
    return &s_wifi_event_group;
}

/*initialize a wifi from AP, STA, APSTA*/
void WIFI_Base_Init(WifiConfigTPDF* tCfg)
{
    wifi_init_config_t wifiApConfig;
    wifi_init_config_t wifiStaConfig;

    if (tCfg->usMode == WIFI_MODE_AP) {
        vWifi_Init_softap(tCfg, &wifiApConfig);
    } else if (tCfg->usMode == WIFI_MODE_STA) {
        vWifi_Init_sta(tCfg, &wifiStaConfig);
    } else if (tCfg->usMode == WIFI_MODE_APSTA) {
        ESP_LOGW(TAG, "Wi-fi ap+sta mode don't support rightnow! ");
    } else {
        ESP_LOGI(TAG, "Wi-fi mode ERROR! ");
    }

    // ESP_ERROR_CHECK(esp_wifi_start());
}